<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h3 data-lake-id="kIIiW" id="kIIiW"><span data-lake-id="u9b1732aa" id="u9b1732aa">背景</span></h3>
  <p data-lake-id="ub0b248b4" id="ub0b248b4"><span data-lake-id="u133ccecd" id="u133ccecd">我的项目中，有一个场景会出现并发问题，就是我们有一个支付单，这个支付单会有到期的自动关闭，以及用户也可以主动取消订单进行关闭。</span></p>
  <p data-lake-id="u23fc9e75" id="u23fc9e75"><span data-lake-id="u97e02b23" id="u97e02b23">​</span><br></p>
  <p data-lake-id="u474514b3" id="u474514b3"><span data-lake-id="ub50844de" id="ub50844de">这两个操作，有的时候就会出现并发的问题，导致重复关单，重复发消息给下游，导致下游处理失败。</span></p>
  <p data-lake-id="u497f1d49" id="u497f1d49"><span data-lake-id="u94682e33" id="u94682e33">​</span><br></p>
  <p data-lake-id="u3687afec" id="u3687afec"><span data-lake-id="u54644469" id="u54644469">为了解决这个并发的问题，我在项目中采用了分布式锁来解决。</span></p>
  <p data-lake-id="uafa07015" id="uafa07015"><br></p>
  <h3 data-lake-id="Jz35o" id="Jz35o"><span data-lake-id="u641e0b24" id="u641e0b24">技术选型</span></h3>
  <p data-lake-id="ua9e08fb7" id="ua9e08fb7"><br></p>
  <p data-lake-id="ub2474091" id="ub2474091"><span data-lake-id="uc5d41d42" id="uc5d41d42">其实分布式锁有很多种方案，无非就是借助一个第三方的系统，来做互斥性的协调。那么市面上主流的实现方式就是基于数据库、zookeeper以及redis。</span></p>
  <p data-lake-id="u59eb68ae" id="u59eb68ae"><span data-lake-id="u9a9a589e" id="u9a9a589e">​</span><br></p>
  <p data-lake-id="u00db6531" id="u00db6531"><span data-lake-id="u020a1ef6" id="u020a1ef6">我在选型的时候分别了解了一下这几个方案。</span></p>
  <p data-lake-id="u85e449ae" id="u85e449ae"><span data-lake-id="uedb6b88f" id="uedb6b88f">​</span><br></p>
  <p data-lake-id="ub9ee42cc" id="ub9ee42cc"><span data-lake-id="ub4be8384" id="ub4be8384">其实最简单的肯定是数据库的方式，在数据库中建一张表，加锁时创建一条记录，解锁时再把记录删除，通过唯一性约束来避免重复的记录。当要加锁的时候，判断下数据库中是否已经有这条记录了，没有就插入一条。</span></p>
  <p data-lake-id="u2e0a736a" id="u2e0a736a"><span data-lake-id="u7c0298bb" id="u7c0298bb">​</span><br></p>
  <p data-lake-id="ue3cda62e" id="ue3cda62e"><span data-lake-id="udf3c5297" id="udf3c5297">这个方案的话好处就是实现简单，缺点也比较明显，就是太依赖数据库，在高并发情况下，可能会对数据库造成压力，并且性能也不太好，而且也会占用很多数据库链接，影响正常的业务。</span></p>
  <p data-lake-id="u8bf5f5ea" id="u8bf5f5ea"><span data-lake-id="u2fccac52" id="u2fccac52">​</span><br></p>
  <p data-lake-id="uf8fff7c7" id="uf8fff7c7"><span data-lake-id="u0af023e8" id="u0af023e8">基于数据库的悲观锁当然也可以实现，但是悲观锁的机制，可能会导致锁表，也会导致线程阻塞的问题，所以这个方案我也放弃了。</span></p>
  <p data-lake-id="uce4a9f1c" id="uce4a9f1c"><span data-lake-id="uc5d784a9" id="uc5d784a9">​</span><br></p>
  <p data-lake-id="u7c48b201" id="u7c48b201"><span data-lake-id="u912151fa" id="u912151fa">另外在zookeeper和redis中，我选择了redis，因为redis我们现在系统中已经在用了，但是zk的话我们没有引入，并且搭建一个zk集群成本也挺高的。</span></p>
  <p data-lake-id="u081c8830" id="u081c8830"><br></p>
  <h3 data-lake-id="dlORN" id="dlORN"><span data-lake-id="u40f52f43" id="u40f52f43">你做了什么</span></h3>
  <p data-lake-id="u9a3d61d4" id="u9a3d61d4"><br></p>
  <p data-lake-id="u6fc5cd9a" id="u6fc5cd9a"><span data-lake-id="u27c976e1" id="u27c976e1">在redis中呢，我们最开始使用了setnx的方案，但是后来经常会出现并发问题，后来发现是因为有的时候，我们设置的解锁的超时时间太短了，导致锁提前释放了。但是拉长超时时间又会降低并发度，后来我发现redisson其实他实现了看门狗的机制，可以帮助我们做自动续期，我就选了redisson的方案。</span></p>
  <p data-lake-id="uf92d68b1" id="uf92d68b1"><span data-lake-id="u981b8a8d" id="u981b8a8d">​</span><br></p>
  <p data-lake-id="ub41d574d" id="ub41d574d"><span data-lake-id="u127e3153" id="u127e3153">但是，其实现在我们这个方案也存在一定的问题，那就是在redis的主节点如果出现问题的情况下，可能会导致锁丢失，但是因为我们这个目前业务量还没有那么大，所以暂时还没有遇到这个问题，不过我也了解过，其实可以基于redlock来解决这个问题，redlock可以借助集群的投票机制，超过半数以上写入成功才算加锁成功，这样可解决单点故障的问题。后面会考虑优化一下。</span></p>
  <p data-lake-id="u3c808651" id="u3c808651"><br></p>
  <h3 data-lake-id="WM9WJ" id="WM9WJ"><span data-lake-id="u8ee5617f" id="u8ee5617f">得到的结果</span></h3>
  <p data-lake-id="u4860923a" id="u4860923a"><br></p>
  <p data-lake-id="u13246626" id="u13246626"><span data-lake-id="u52f2be2a" id="u52f2be2a">解决了并发的问题</span></p>
  <p data-lake-id="ua54dff47" id="ua54dff47"><br></p>
  <p data-lake-id="uc0446b83" id="uc0446b83"><br></p>
  <p data-lake-id="ubab56739" id="ubab56739"><strong><span data-lake-id="u358eb30b" id="u358eb30b" class="lake-fontsize-14" style="color: rgb(38, 38, 38)">学习资料</span></strong><span data-lake-id="uce033b41" id="uce033b41" class="lake-fontsize-14" style="color: rgb(38, 38, 38)"><br></span><span data-lake-id="u5b9b26d2" id="u5b9b26d2" style="color: rgb(38, 38, 38)"><br><br></span></p>
  <p data-lake-id="u33957964" id="u33957964"><span data-lake-id="u311473c7" id="u311473c7">​</span><br></p>
  <p data-lake-id="u6ac3b7aa" id="u6ac3b7aa"><br></p>
  <p data-lake-id="u3d15417e" id="u3d15417e"><br></p>
  <p data-lake-id="u3f47e9bd" id="u3f47e9bd"><br></p>
 </body>
</html>